#ifndef PCPS_ICOSIDODECAHEDRON_HEADER
#define PCPS_ICOSIDODECAHEDRON_HEADER

#include<set>
#include<vector>

#include<src/exception.h>

#include<boost/format.hpp>

namespace pcps {

  inline std::set<int> icosidod_get_a_sites() {
    int a_site_array [10] = { 27, 21, 23,  1,  2, 17,  7, 11, 15,  6 };
    for (int i = 0; i < 10; i++)
      a_site_array [i] -= 1;
    return std::set<int>(a_site_array, a_site_array + 10);
  }

  inline std::set<int> icosidod_get_b_sites() {
    int b_site_array [10] = { 24, 28, 22, 26,  3,  5, 14, 16, 10, 12 };
    for (int i = 0; i < 10; i++)
      b_site_array [i] -= 1;
    return std::set<int>(b_site_array, b_site_array + 10);
  }

  inline std::set<int> icosidod_get_c_sites() {
    int c_site_array [10] = { 30, 25, 29, 19, 20,  4,  8, 18, 13,  9 };
    for (int i = 0; i < 10; i++)
      c_site_array [i] -= 1;
    return std::set<int>(c_site_array, c_site_array + 10);
  }

  inline std::set< std::set<int> > icosidod_get_pentagons() {
    std::set< std::set<int> > retval;
    { int s[5] = {14, 15, 17, 18, 20}; retval.insert( std::set<int>(s, s+5) ); }
    { int s[5] = { 8,  9, 11, 12, 14}; retval.insert( std::set<int>(s, s+5) ); }
    { int s[5] = { 2,  3,  5,  6,  8}; retval.insert( std::set<int>(s, s+5) ); }
    { int s[5] = { 0,  2, 26, 27, 29}; retval.insert( std::set<int>(s, s+5) ); }
    { int s[5] = {20, 21, 23, 24, 26}; retval.insert( std::set<int>(s, s+5) ); }
    { int s[5] = {10, 12, 13, 15, 16}; retval.insert( std::set<int>(s, s+5) ); }
    { int s[5] = { 4,  6,  7,  9, 10}; retval.insert( std::set<int>(s, s+5) ); }
    { int s[5] = { 0,  1,  3,  4, 28}; retval.insert( std::set<int>(s, s+5) ); }
    { int s[5] = {22, 24, 25, 27, 28}; retval.insert( std::set<int>(s, s+5) ); }
    { int s[5] = {16, 18, 19, 21, 22}; retval.insert( std::set<int>(s, s+5) ); }
    { int s[5] = { 1,  7, 13, 19, 25}; retval.insert( std::set<int>(s, s+5) ); }
    { int s[5] = { 5, 11, 17, 23, 29}; retval.insert( std::set<int>(s, s+5) ); }
    return retval;
  }

  inline std::set< std::set<int> > icosidod_get_one_pent_triples() {

    std::set< std::set<int> > retval;

    { int s[3] = {17, 23, 29}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {23, 29,  5}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {29,  5, 11}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 5, 11, 17}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {11, 17, 23}; retval.insert( std::set<int>(s, s+3) ); }

    { int s[3] = { 1,  7, 13}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 7, 13, 19}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {13, 19, 25}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {19, 25,  1}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {25,  1,  7}; retval.insert( std::set<int>(s, s+3) ); }

    { int s[3] = {17, 20, 18}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {20, 18, 15}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {18, 15, 14}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {15, 14, 17}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {14, 17, 20}; retval.insert( std::set<int>(s, s+3) ); }

    { int s[3] = {23, 26, 24}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {26, 24, 21}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {24, 21, 20}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {21, 20, 23}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {20, 23, 26}; retval.insert( std::set<int>(s, s+3) ); }

    { int s[3] = {26, 29,  2}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {29,  2,  0}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 2,  0, 27}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 0, 27, 26}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {27, 26, 29}; retval.insert( std::set<int>(s, s+3) ); }

    { int s[3] = { 2,  5,  8}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 5,  8,  6}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 8,  6,  3}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 6,  3,  2}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 3,  2,  5}; retval.insert( std::set<int>(s, s+3) ); }

    { int s[3] = { 8, 11, 14}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {11, 14, 12}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {14, 12,  9}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {12,  9,  8}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 9,  8, 11}; retval.insert( std::set<int>(s, s+3) ); }

    { int s[3] = {15, 16, 13}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {16, 13, 10}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {13, 10, 12}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {10, 12, 15}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {12, 15, 16}; retval.insert( std::set<int>(s, s+3) ); }

    { int s[3] = {18, 21, 22}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {21, 22, 19}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {22, 19, 16}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {19, 16, 18}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {16, 18, 21}; retval.insert( std::set<int>(s, s+3) ); }

    { int s[3] = {24, 27, 28}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {27, 28, 25}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {28, 25, 22}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {25, 22, 24}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {22, 24, 27}; retval.insert( std::set<int>(s, s+3) ); }

    { int s[3] = { 0,  3,  4}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 3,  4,  1}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 4,  1, 28}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 1, 28,  0}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {28,  0,  3}; retval.insert( std::set<int>(s, s+3) ); }

    { int s[3] = { 4,  6,  9}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 6,  9, 10}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 9, 10,  7}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {10,  7,  4}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 7,  4,  6}; retval.insert( std::set<int>(s, s+3) ); }

    for (int i = 0; i < 30; i++) {
      int c = 0;
      for (std::set< std::set<int> >::const_iterator triple = retval.begin(); triple != retval.end(); triple++)
        c += triple->count(i);
      if (c != 6)
        throw pcps::Exception( (boost::format("expected site %i to appear 6 times in one pentagon triples") % i).str() );
    }

    return retval;

  }

  inline std::set< std::set<int> > icosidod_get_two_pent_triples() {

    std::set< std::set<int> > retval;

    { int s[3] = { 0,  2, 28}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 0,  3, 27}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 1,  4, 25}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 1,  7, 28}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 2,  0,  5}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 2,  3, 29}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 3,  0,  6}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 3,  2,  4}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 4,  3,  7}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 4,  1,  6}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 5,  2, 11}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 5,  8, 29}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 6,  3,  9}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 6,  4,  8}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 7,  1, 10}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 7,  4, 13}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 8,  6, 11}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 8,  5,  9}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 9,  6, 12}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = { 9,  8, 10}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {10,  9, 13}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {10,  7, 12}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {11,  8, 17}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {11,  5, 14}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {12, 10, 14}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {12,  9, 15}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {13,  7, 16}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {13, 10, 19}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {14, 11, 15}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {14, 12, 17}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {15, 12, 18}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {15, 14, 16}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {16, 13, 18}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {16, 15, 19}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {17, 14, 23}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {17, 11, 20}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {18, 15, 21}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {18, 16, 20}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {19, 16, 25}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {19, 13, 22}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {20, 17, 21}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {20, 18, 23}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {21, 18, 24}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {21, 20, 22}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {22, 19, 24}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {22, 21, 25}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {23, 20, 29}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {23, 17, 26}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {24, 22, 26}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {24, 21, 27}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {25,  1, 22}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {25, 19, 28}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {26, 24, 29}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {26, 23, 27}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {27,  0, 24}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {27, 26, 28}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {28,  0, 25}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {28,  1, 27}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {29,  5, 26}; retval.insert( std::set<int>(s, s+3) ); }
    { int s[3] = {29,  2, 23}; retval.insert( std::set<int>(s, s+3) ); }

    for (int i = 0; i < 30; i++) {
      int c = 0;
      for (std::set< std::set<int> >::const_iterator triple = retval.begin(); triple != retval.end(); triple++)
        c += triple->count(i);
      if (c != 6)
        throw pcps::Exception( (boost::format("expected site %i to appear 6 times in two pentagon triples") % i).str() );
    }

    return retval;

  }

  // get the triangles
  inline std::set< std::set<int> > icosidod_get_three_pent_triples() {

    std::set< std::set<int> > retval;

    { std::set<int> s; s.insert(24-1); s.insert(27-1); s.insert(30-1); retval.insert(s); }
    { std::set<int> s; s.insert(30-1); s.insert( 3-1); s.insert( 6-1); retval.insert(s); }
    { std::set<int> s; s.insert( 6-1); s.insert( 9-1); s.insert(12-1); retval.insert(s); }
    { std::set<int> s; s.insert(12-1); s.insert(15-1); s.insert(18-1); retval.insert(s); }
    { std::set<int> s; s.insert(18-1); s.insert(21-1); s.insert(24-1); retval.insert(s); }
    { std::set<int> s; s.insert( 1-1); s.insert( 3-1); s.insert( 4-1); retval.insert(s); }
    { std::set<int> s; s.insert( 7-1); s.insert( 9-1); s.insert(10-1); retval.insert(s); }
    { std::set<int> s; s.insert(13-1); s.insert(15-1); s.insert(16-1); retval.insert(s); }
    { std::set<int> s; s.insert(19-1); s.insert(21-1); s.insert(22-1); retval.insert(s); }
    { std::set<int> s; s.insert(25-1); s.insert(27-1); s.insert(28-1); retval.insert(s); }
    { std::set<int> s; s.insert( 4-1); s.insert( 5-1); s.insert( 7-1); retval.insert(s); }
    { std::set<int> s; s.insert(10-1); s.insert(11-1); s.insert(13-1); retval.insert(s); }
    { std::set<int> s; s.insert(16-1); s.insert(17-1); s.insert(19-1); retval.insert(s); }
    { std::set<int> s; s.insert(22-1); s.insert(23-1); s.insert(25-1); retval.insert(s); }
    { std::set<int> s; s.insert(28-1); s.insert(29-1); s.insert( 1-1); retval.insert(s); }
    { std::set<int> s; s.insert( 2-1); s.insert( 5-1); s.insert( 8-1); retval.insert(s); }
    { std::set<int> s; s.insert( 8-1); s.insert(11-1); s.insert(14-1); retval.insert(s); }
    { std::set<int> s; s.insert(14-1); s.insert(17-1); s.insert(20-1); retval.insert(s); }
    { std::set<int> s; s.insert(20-1); s.insert(23-1); s.insert(26-1); retval.insert(s); }
    { std::set<int> s; s.insert(26-1); s.insert(29-1); s.insert( 2-1); retval.insert(s); }

    return retval;

  }

  inline std::set< std::set<int> > icosidod_get_nearest_pairs() {

    std::set< std::set<int> > retval;

    // for each triangle, create its three nearest neighbor pairs
    const std::set< std::set<int> > triangles = pcps::icosidod_get_three_pent_triples();
    for (std::set< std::set<int> >::const_iterator t = triangles.begin(); t != triangles.end(); t++) {
      assert(t->size() == 3);
      std::set<int>::const_iterator i = t->begin();
      std::set<int>::const_iterator j = t->begin();
      { j++; assert(*i >= 0 && *j > *i); std::set<int> s; s.insert(*i); s.insert(*j); retval.insert(s); }
      { j++; assert(*i >= 0 && *j > *i); std::set<int> s; s.insert(*i); s.insert(*j); retval.insert(s); }
      { i++; assert(*i >= 0 && *j > *i); std::set<int> s; s.insert(*i); s.insert(*j); retval.insert(s); }
    }

    return retval;

  }

  inline std::set< std::set<int> > icosidod_get_next_nearest_pairs() {

    // get all nearest neighbor pairs
    const std::set< std::set<int> > nearest_pairs = pcps::icosidod_get_nearest_pairs();

    // create a list of sites' neighbors
    std::vector< std::set<int> > neighbors(30);
    for (std::set< std::set<int> >::const_iterator pair = nearest_pairs.begin(); pair != nearest_pairs.end(); pair++) {
      std::set<int>::const_iterator i = pair->begin();
      std::set<int>::const_iterator j = pair->begin(); j++;
      neighbors.at(*i).insert(*j);
      neighbors.at(*j).insert(*i);
    }

    // create pairs of next nearest neighbors
    std::set< std::set<int> > retval;
    for (int i = 0; i < neighbors.size(); i++) {
      std::set<int>::const_iterator j, k;
      for (j = neighbors.at(i).begin(); j != neighbors.at(i).end(); j++)
      for (         (k = j)++         ; k != neighbors.at(i).end(); k++)
        if (neighbors.at(*k).count(*j) == 0 && neighbors.at(*j).count(*k) == 0)
          { std::set<int> s; s.insert(*j); s.insert(*k); retval.insert(s); }
    }

    if (retval.size() != 120) throw pcps::Exception("wrong number of next nearest neighbor pairs");

    for (std::set< std::set<int> >::const_iterator pair = retval.begin(); pair != retval.end(); pair++)
      if (pair->size() != 2)
        throw pcps::Exception("wrong number of elements in a next nearest neighbor pair");

    return retval;

  }

  inline std::set< std::set<int> > icosidod_get_stars() {

    // get all nearest neighbor pairs
    const std::set< std::set<int> > nearest_pairs = pcps::icosidod_get_nearest_pairs();

    // create a list of sites' neighbors
    std::vector< std::set<int> > neighbors(30);
    for (std::set< std::set<int> >::const_iterator pair = nearest_pairs.begin(); pair != nearest_pairs.end(); pair++) {
      std::set<int>::const_iterator i = pair->begin();
      std::set<int>::const_iterator j = pair->begin(); j++;
      neighbors.at(*i).insert(*j);
      neighbors.at(*j).insert(*i);
    }

    // get pentagons
    const std::set< std::set<int> > pentagons = pcps::icosidod_get_pentagons();

    // for each pentagon, add the touching triangles
    std::set< std::set<int> > retval;
    for (std::set< std::set<int> >::const_iterator pent = pentagons.begin(); pent != pentagons.end(); pent++) {
      std::set<int> sites(*pent);
      for (std::set<int>::const_iterator site = pent->begin(); site != pent->end(); site++)
        for (std::set<int>::const_iterator neighbor = neighbors.at(*site).begin(); neighbor != neighbors.at(*site).end(); neighbor++)
          sites.insert(*neighbor);
      retval.insert(sites);
    }

    return retval;

  }

}

#endif
